﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pipes.Queries.MetaData;

namespace Pipes.Queries.Parser
{
  public class DisambiguationVisitor : IQueryVisitor
  {
    private Provider.IProvider _provider;
    private Dictionary<string, IEnumerable<string>> _aliases = new Dictionary<string, IEnumerable<string>>();
    private List<RecordSet> _tables = new List<RecordSet>();

    public DisambiguationVisitor(Provider.IProvider provider)
    {
      _provider = provider;
    }

    public void Visit(Model.Query.Conditional value)
    {
      GeneralDataType type = GeneralDataType.unknown;
      for (var i = 0; i < value.Branches.Count; i++)
      {
        value.Branches[i].Visit(this);
        if (i == 0)
        {
          type = value.Branches[i].Branch.InferredType;
        }
        else if (type != value.Branches[i].Branch.InferredType)
        {
          type = GeneralDataType.unknown;
        }
      }
      value.Else.Visit(this);
      if (value.Branches.Count == 0)
      {
        type = value.Else.InferredType;
      }
      else if (type != value.Else.InferredType)
      {
        type = GeneralDataType.unknown;
      }
    }

    public void Visit(Model.Query.ConditionBranch value)
    {
      value.Condition.Visit(this);
      value.Branch.Visit(this);
    }

    public void Visit(Model.Query.Constant value)
    {
      // Do Nothing
    }

    public void Visit(Model.Query.DateLiteral value)
    {
      // Do Nothing
    }

    public void Visit(Model.Query.Function value)
    {
      value.Name.Visit(this);
      for (var i = 0; i < value.Args.Count; i++)
      {
        value.Args[i].Visit(this);
      }
      value.InferredType = value.Name.InferredType;
    }

    public void Visit(Model.Query.Identifier value)
    {
      var parts = value.Parts.ToList();
      IEnumerable<string> orig = null;
      if (_aliases.TryGetValue(parts[0], out orig))
      {
        parts.RemoveAt(0);
        parts.InsertRange(0, orig);
      }
      var meta = _provider.GetMetaData(parts);
      if (meta == null && parts.Count == 1)
      {
        bool found = false;
        foreach (var table in _tables)
        {
          if (Utils.TryGetValue(table.Columns, parts[0], out meta))
          {
            if (found)
            {
              // Ambiguous
              meta = null;
              break;
            }
            else
            {
              found = true;
            }
          }
        }
      }

      if (meta != null)
      {
        value.InferredType = meta.InferredType;

        // Add the table
        var table = meta as RecordSet;
        if (table != null) _tables.Add(table);
      }
    }

    public void Visit(Model.Query.Join value)
    {
      Visit((Model.Query.SimpleDeclaration)value);
      value.Criteria.Visit(this);
    }

    public void Visit(Model.Query.LogicalNot value)
    {
      value.Operand.Visit(this);
    }

    public void Visit(Model.Query.NumericLiteral value)
    {
      // Do Nothing
    }

    public void Visit(Model.Query.Operation value)
    {
      switch(value.Type)
      {
        case Model.Query.Operator.Ambig_AdditionConcat:
        case Model.Query.Operator.Ambig_BitwiseAndConcat:
        case Model.Query.Operator.Ambig_LogicalOrConcat:
          bool isString = false;
          foreach (var arg in value.Args)
          {
            if (arg.InferredType == GeneralDataType.@string)
            {
              isString = true;
              break;
            }
          }
          if (isString)
          {
            value.Type = Model.Query.Operator.Concatenation;
          }
          else
          {
            switch (value.Type)
            {
              case Model.Query.Operator.Ambig_AdditionConcat:
                value.Type = Model.Query.Operator.Addition;
                break;
              case Model.Query.Operator.Ambig_BitwiseAndConcat:
                value.Type = Model.Query.Operator.BitwiseAnd;
                break;
              case Model.Query.Operator.Ambig_LogicalOrConcat:
                value.Type = Model.Query.Operator.LogicalOr;
                break;
            }
          }
          break;
      }
    }

    public void Visit(Model.Query.OrderSpec value)
    {
      // Do Nothing
    }

    public void Visit(Model.Pattern.PatternOpts value)
    {
      // Do Nothing
    }

    public void Visit(Model.Query.ReadBlock value)
    {
      for (var i = 0; i < value.ReadStatements.Count; i++)
      {
        value.ReadStatements[i].Visit(this);
      }
    }

    public void Visit(Model.Query.ReadStatement value)
    {
      // Need to create new context for each select statement
      for (var i = 0; i < value.Source.Count; i++)
      {
        value.Source[i].Visit(this);
      }
      for (var i = 0; i < value.Columns.Count; i++)
      {
        value.Columns[i].Visit(this);
      }
      if (value.Where != null)
      {
        value.Where.Visit(this);
      }
      for (var i = 0; i < value.GroupBy.Count; i++)
      {
        value.GroupBy[i].Visit(this);
      }
      if (value.Having != null)
      {
        value.Having.Visit(this);
      }
      for (var i = 0; i < value.OrderBy.Count; i++)
      {
        value.OrderBy[i].Visit(this);
      }
    }

    public void Visit(Model.Query.Script value)
    {
      for (var i = 0; i < value.Statements.Count; i++)
      {
        value.Statements[i].Visit(this);
      }
    }

    public void Visit(Model.Query.SimpleDeclaration value)
    {
      value.Expr.Visit(this);
      if (value.Alias != null && !string.IsNullOrEmpty(value.Alias.ToString()))
      {
        var ident = value.Expr as Model.Query.Identifier;
        if (ident != null)
        {
          _aliases[value.Alias.ToString('\0', '.')] = ident.Parts;
        }
      }
    }

    public void Visit(Model.Query.StringLiteral value)
    {
      // Do Nothing
    }

    public void Visit(Model.Query.UpdateStatement value)
    {
      value.Source.Visit(this);
      for (var i = 0; i < value.Assignments.Count; i++)
      {
        value.Assignments[i].Visit(this);
      }
      if (value.Where != null)
      {
        value.Where.Visit(this);
      }
    }
  }
}
